#ifndef SIMPLE_SUPPORT_ITERATOR_OUT_TRANSFORM_HPP
#define SIMPLE_SUPPORT_ITERATOR_OUT_TRANSFORM_HPP

#include <utility> // std::forward std::swap
#include <iterator> // std::iterator_traits
#include <type_traits> // std::enable_if_t std::true_type std::false_type
#include <cstddef> // std::nullptr_t

#include "../function_utils.hpp" // support::invoke

namespace simple::support
{

	struct iterator_proxy_tag_t {};

	template <typename It, typename Transform>
	struct out_transform
	{
		using value_type = typename std::iterator_traits<It>::value_type;
		using difference_type = typename std::iterator_traits<It>::difference_type;
		using pointer = typename std::iterator_traits<It>::pointer;
		using reference = typename std::iterator_traits<It>::reference;
		using iterator_category = typename std::iterator_traits<It>::iterator_category;
		It out;
		Transform transform;

		template <typename T>
		out_transform(T&& transform, It out)
			: out(out), transform(std::forward<T>(transform))
		{}

		out_transform& operator++() { ++out; return *this; }
		out_transform operator++(int) { out_transform prev = *this; ++(*this); return prev; }
		out_transform& operator--() { --out; return *this; }
		out_transform operator--(int) { out_transform prev = *this; --(*this); return prev; }

		class proxy
		{
			It& out;
			Transform& transform;

			proxy(It& out, Transform& transform) : out(out), transform(transform) {}
			friend struct out_transform;
			public:
			using ref = reference;
			constexpr static iterator_proxy_tag_t iterator_proxy_tag{};

			proxy() = delete;
			proxy(const proxy&) = delete;
			proxy(const proxy&&) = delete;
			proxy& operator=(const proxy&) = delete;
			proxy& operator=(const proxy&&) = delete;

			template <typename T>
			const proxy& operator=(T&& x) const
			{
				*out = support::invoke(transform, std::forward<T>(x));
				return *this;
			}

			operator ref() const
			{
				return *out;
			}

		};

		const proxy operator*()
		{
			return {out, transform};
		}
	};
	template <typename I, typename T> out_transform(T&&, I) -> out_transform<I, T>;

	namespace adl_helper
	{
		using std::swap;

		template <typename T>
		constexpr void swap_(T& a, T& b)
		noexcept(noexcept(swap(a,b)))
		{ swap(a,b); }
	} // namespace adl_helper

	// what a chore -_-
	std::nullptr_t accept_iterator_proxy_tag(iterator_proxy_tag_t);

	template <typename T, typename = std::nullptr_t>
	struct is_iterator_proxy : std::false_type {};
	template <typename T>
	struct is_iterator_proxy<T, decltype(accept_iterator_proxy_tag(T::iterator_proxy_tag))> : std::true_type {};
	template <typename T>
	constexpr bool is_iterator_proxy_v = is_iterator_proxy<T>::value;

	template <typename Proxy, std::enable_if_t<is_iterator_proxy_v<Proxy>>* = nullptr>
	constexpr
	void swap(const Proxy& a, typename Proxy::ref& b)
	noexcept(noexcept(adl_helper::swap_(a,b)))
	{ adl_helper::swap_(static_cast<typename Proxy::ref>(a),b); }
	template <typename Proxy, std::enable_if_t<is_iterator_proxy_v<Proxy>>* = nullptr>
	constexpr
	void swap(typename Proxy::ref& a, const Proxy& b)
	noexcept(noexcept(adl_helper::swap_(a,static_cast<typename Proxy::ref>(b))))
	{ adl_helper::swap_(a,static_cast<typename Proxy::ref>(b)); }

} // namespace simple::support

#endif /* end of include guard */
